{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.14    11/12/2004 3:44:00 PM  JPMugaas
  Compiler error fix.  OOPPS!!!

  Rev 1.13    11/12/2004 11:30:20 AM  JPMugaas
  Expansions for IPv6.

  Rev 1.12    6/11/2004 11:48:34 PM  JPMugaas
  Fix for mistake I made.  UDPReceive should have been UDPException

  Rev 1.11    6/11/2004 4:05:34 PM  JPMugaas
  RecvFrom should now work in the UDP server with IPv6.
  An OnException event was added for logging purposes.

  Rev 1.10    09/06/2004 00:25:32  CCostelloe
  Kylix 3 patch

  Rev 1.9    2004.02.03 4:17:02 PM  czhower
  For unit name changes.

  Rev 1.8    2004.01.20 10:03:40 PM  czhower
  InitComponent

  Rev 1.7    2003.12.31 8:03:36 PM  czhower
  Matched visibility

  Rev 1.6    10/26/2003 6:01:44 PM  BGooijen
  Fixed binding problem

  Rev 1.5    10/24/2003 5:18:38 PM  BGooijen
  Removed boolean shortcutting from .GetActive

  Rev 1.4    10/22/2003 04:41:02 PM  JPMugaas
  Should compile with some restored functionality.  Still not finished.

  Rev 1.3    2003.10.11 9:58:50 PM  czhower
  Started on some todos

  Rev 1.2    2003.10.11 5:52:18 PM  czhower
  -VCL fixes for servers
  -Chain suport for servers (Super core)
  -Scheduler upgrades
  -Full yarn support

  Rev 1.1    2003.09.30 1:23:10 PM  czhower
  Stack split for DotNet

  Rev 1.0    11/13/2002 09:02:30 AM  JPMugaas
}

unit IdUDPServer;

interface
{$I IdCompilerDefines.inc}
//Put FPC into Delphi mode
uses
  IdComponent,
  IdException,
  IdGlobal,
  IdObjs,
  IdSocketHandle,
  IdStackConsts,
  IdSys,
  IdThread,
  IdUDPBase,
  IdStack;

type
  //Exception is used instead of EIdException because the exception could be from somewhere else
  TIdUDPExceptionEvent = procedure(Sender :TObject; ABinding: TIdSocketHandle; const AMessage : String; const AExceptionClass : TClass) of object;
  TUDPReadEvent = procedure(Sender: TObject; AData: TIdBytes; ABinding: TIdSocketHandle) of object;

  TIdUDPServer = class;

  TIdUDPListenerThread = class(TIdThread)
  protected
    FBinding: TIdSocketHandle;
    FAcceptWait: Integer;
    FBuffer: TIdBytes;

    FCurrentException: String;
    FCurrentExceptionClass: TClass;
    //
    procedure AfterRun; override;
    procedure Run; override;
  public
    FServer: TIdUDPServer;
    //
    constructor Create(AOwner: TIdUDPServer; ABinding: TIdSocketHandle); reintroduce;
    destructor Destroy; override;

    procedure UDPRead;
    procedure UDPException;
    //
    property AcceptWait: integer read FAcceptWait write FAcceptWait;
    property Binding: TIdSocketHandle read FBinding;
  end;

  TIdUDPServer = class(TIdUDPBase)
  protected
    FBindings: TIdSocketHandles;
    FCurrentBinding: TIdSocketHandle;
    FListenerThreads: TIdThreadList;
    FOnUDPRead: TUDPReadEvent;
    FOnUDPException : TIdUDPExceptionEvent;
    FThreadedEvent: boolean;
    //
    procedure BroadcastEnabledChanged; override;
    procedure CloseBinding; override;
    procedure DoOnUDPException(ABinding: TIdSocketHandle; const AMessage : String; const AExceptionClass : TClass);  virtual;
    procedure DoUDPRead(AData: TIdBytes; ABinding: TIdSocketHandle); virtual;
    function GetActive: Boolean; override;
    function GetBinding: TIdSocketHandle; override;
    function GetDefaultPort: integer;
    procedure InitComponent; override;
    procedure SetBindings(const Value: TIdSocketHandles);
    procedure SetDefaultPort(const AValue: integer);
  public
    destructor Destroy; override;
  published
    property Bindings: TIdSocketHandles read FBindings write SetBindings;
    property DefaultPort: integer read GetDefaultPort write SetDefaultPort;
    property OnUDPRead: TUDPReadEvent read FOnUDPRead write FOnUDPRead;
    property OnUDPException : TIdUDPExceptionEvent read FOnUDPException write FOnUDPException;
    property ThreadedEvent: boolean read FThreadedEvent write FThreadedEvent default False;
  end;
  EIdUDPServerException = class(EIdUDPException);

implementation

uses IdGlobalCore;

procedure TIdUDPServer.BroadcastEnabledChanged;
var
  i: integer;
begin
  if Assigned(FCurrentBinding) then begin
    for i := 0 to Bindings.Count - 1 do begin
      SetBroadcastFlag(BroadcastEnabled, Bindings[i]);
    end;
  end;
end;

procedure TIdUDPServer.CloseBinding;
var
  LListenerThreads: TIdList;
begin
  // RLebeau 2/17/2006: TIdUDPBase.Destroy() calls CloseBinding()
  if Assigned(FListenerThreads) then
  begin
    LListenerThreads := FListenerThreads.LockList;
    try
      while LListenerThreads.Count > 0 do
      begin
        with TIdUDPListenerThread(LListenerThreads[0]) do begin
          // Stop listening
          Stop;
          Binding.CloseSocket;
          // Tear down Listener thread
          WaitFor;
          Free;
        end;
        LListenerThreads.Delete(0); // RLebeau 2/17/2006
      end;
    finally
      FListenerThreads.UnlockList;
    end;
  end;
  FCurrentBinding := nil;
end;

destructor TIdUDPServer.Destroy;
begin
  Active := False;
  Sys.FreeAndNil(FBindings);
  Sys.FreeAndNil(FListenerThreads);
  inherited Destroy;
end;

procedure TIdUDPServer.DoOnUDPException(ABinding: TIdSocketHandle; const AMessage : String; const AExceptionClass : TClass);
begin
  if Assigned(FOnUDPException) then begin
    OnUDPException(Self, ABinding, AMessage, AExceptionClass);
  end;
end;

procedure TIdUDPServer.DoUDPRead(AData: TIdBytes; ABinding: TIdSocketHandle);
begin
  if Assigned(OnUDPRead) then begin
    OnUDPRead(Self, AData, ABinding);
  end;
end;

function TIdUDPServer.GetActive: Boolean;
begin
  // inherited GetActive keeps track of design-time Active property
  Result := inherited GetActive;
  if not Result then begin
    if Assigned(FCurrentBinding) then begin
      Result := FCurrentBinding.HandleAllocated;
    end;
  end;
end;

function TIdUDPServer.GetBinding: TIdSocketHandle;
var
  LListenerThread: TIdUDPListenerThread;
  i: Integer;
begin
  if FCurrentBinding = nil then begin
    if Bindings.Count = 0 then begin
      Bindings.Add; // IPv4
      if GStack.SupportsIPv6 then begin
        // maybe add a property too, so the developer can switch it on/off
        Bindings.Add.IPVersion := Id_IPv6;
      end;
    end;

    // Set up listener threads
    i := 0;
    try
      while i < Bindings.Count do begin
        with Bindings[i] do begin
{$IFDEF LINUX}
      Bindings[i].AllocateSocket(LongInt(Id_SOCK_DGRAM));
{$ELSE}
      Bindings[i].AllocateSocket(Id_SOCK_DGRAM);
{$ENDIF}
          Bind;
        end;
        Inc(i);
      end;
    except
      Dec(i); // the one that failed doesn't need to be closed
      while i >= 0 do begin
        Bindings[i].CloseSocket;
        Dec(i);
      end;
      raise;
    end;
    for i := 0 to Bindings.Count - 1 do begin
      LListenerThread := TIdUDPListenerThread.Create(Self, Bindings[i]);
      LListenerThread.Name := Name + ' Listener #' + Sys.IntToStr(i + 1); {do not localize}
      //Todo: Implement proper priority handling for Linux
      //http://www.midnightbeach.com/jon/pubs/2002/BorCon.London/Sidebar.3.html
      LListenerThread.Priority := tpListener;
      FListenerThreads.Add(LListenerThread);
      LListenerThread.Start;
    end;
    FCurrentBinding := Bindings[0];
    BroadcastEnabledChanged;
  end;
  Result := FCurrentBinding;
end;

function TIdUDPServer.GetDefaultPort: integer;
begin
  Result := FBindings.DefaultPort;
end;

procedure TIdUDPServer.InitComponent;
begin
  inherited InitComponent;
  FBindings := TIdSocketHandles.Create(Self);
  FListenerThreads := TIdThreadList.Create;
end;

procedure TIdUDPServer.SetBindings(const Value: TIdSocketHandles);
begin
  // TODO: update the listener threads as well
  FBindings.Assign(Value);
end;

procedure TIdUDPServer.SetDefaultPort(const AValue: integer);
begin
  FBindings.DefaultPort := AValue;
end;

{ TIdUDPListenerThread }

procedure TIdUDPListenerThread.AfterRun;
begin
  inherited AfterRun;
  // Close just own binding. The rest will be closed from their
  // coresponding threads
  FBinding.CloseSocket;
end;

constructor TIdUDPListenerThread.Create(AOwner: TIdUDPServer; ABinding: TIdSocketHandle);
begin
  inherited Create(True);
  FAcceptWait := 1000;
  FBinding := ABinding;
  FServer := AOwner;
  SetLength(FBuffer, 0);
end;

destructor TIdUDPListenerThread.Destroy;
begin
  SetLength(FBuffer, 0);
  inherited Destroy;
end;

procedure TIdUDPListenerThread.Run;
var
  PeerIP: string;
  PeerPort : TIdPort;
  ByteCount: Integer;
begin
  if FBinding.Select(AcceptWait) then try
    // Doublecheck to see if we've been stopped
    // Depending on timing - may not reach here if it is in ancestor run when thread is stopped
    if not Stopped then begin
      SetLength(FBuffer, FServer.BufferSize);
      ByteCount := GStack.ReceiveFrom(FBinding.Handle, FBuffer, PeerIP, PeerPort, FBinding.IPVersion);
      SetLength(FBuffer, ByteCount);
      FBinding.SetPeer(PeerIP, PeerPort, FBinding.IPVersion);
      if FServer.ThreadedEvent then begin
        UDPRead;
      end else begin
        Synchronize(UDPRead);
      end;
    end;
  except
    // exceptions should be ignored so that other clients can be served in case of a DOS attack
    on E : Exception do
    begin
      FCurrentException := E.Message;
      FCurrentExceptionClass := E.ClassType;
      if FServer.ThreadedEvent then begin
        UDPException;
      end else begin
        Synchronize(UDPException);
      end;
    end;
  end;
end;

procedure TIdUDPListenerThread.UDPRead;
begin
  FServer.DoUDPRead(FBuffer, FBinding);
end;

procedure TIdUDPListenerThread.UDPException;
begin
  FServer.DoOnUDPException(FBinding, FCurrentException, FCurrentExceptionClass);
end;

end.
